跳到主要内容

应用菜单和右键菜单怎么做?macOS 为什么必须 setApplicationMenu?

考察点​

  • Menu 的两种用法
  • macOS 应用菜单的平台习惯

参考答案​

1. 应用菜单​

macOS 的屏幕顶部菜单不属于某一个窗口。不调用 Menu.setApplicationMenu,复制粘贴等快捷键会丢,用户也找不到退出和关于。

const { Menu } = require('electron');

const template = [
{
// macOS 第一项必须是应用名菜单
label: app.name,
submenu: [
{ role: 'about' },
{ type: 'separator' },
{ role: 'quit' },
],
},
{
label: '编辑',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'selectAll' },
],
},
];

Menu.setApplicationMenu(Menu.buildFromTemplate(template));

role 让 Electron 填上平台对应的快捷键和文案。自己写 click 时,快捷键用 accelerator: 'CommandOrControl+F'。

Windows 上这套菜单出现在窗口顶上。如果窗口是 frame: false,应用菜单往往被拿掉,改成页面里的菜单,但复制粘贴仍建议留一份隐藏菜单或自己处理,否则输入框里的角色键会丢。

2. 右键菜单​

在主进程弹,坐标来自页面:

ipcMain.on('menu:context', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
const menu = Menu.buildFromTemplate([
{ label: '刷新', click: () => win.webContents.reload() },
{ type: 'separator' },
{ role: 'copy' },
]);
menu.popup({ window: win });
});

不要在生产菜单里留“打开 DevTools”。检查更新、设置、退出可以放托盘菜单和应用菜单各一份,两边调用同一函数。

3. 菜单项动态变化​

Menu 建好后改单项不方便,状态变了就重新 buildFromTemplate 再 setApplicationMenu。不要在渲染进程里拼一套和系统菜单行为不一致的快捷键,除非产品明确要求自定义。

面试话术​

macOS 菜单在屏幕顶部,必须 Menu.setApplicationMenu,第一项是应用菜单,编辑菜单用 role 保住复制粘贴。右键用 Menu.popup,挂到 event.sender 对应的窗口。无边框窗口拿掉菜单时要确认输入框的复制粘贴还在。生产菜单不放 DevTools。

常见追问​

  • click 里怎么把菜单动作送到页面? 菜单在主进程,win.webContents.send 通知当前窗口。窗口不存在就先建窗口。
  • 和 globalShortcut 冲突吗? 菜单 accelerator 在应用聚焦时生效;应用不聚焦仍要响应的,才用 globalShortcut。同一组合键不要两处都注册。